/*
 * @FileName: boot.c
 * @Author: GreyQiu qiushaogui@aikosolar.com
 * @Date: 2023-08-23 17:36:00
 * @LastEditors: GreyQiu qiushaogui@aikosolar.com
 * @LastEditTime: 2023-09-23 09:54:50
 * @Description: 
 * 
 * Copyright (c) 2023, All Rights Reserved. 
 */

#include "boot.h"
#include "bsp.h"
#include "log.h"
#include "crc.h"
#include "easy_timer.h"

boot_obj_t boot;
app_head_t app_head;

flash_obj_t app1_obj;
flash_obj_t app2_obj;
flash_obj_t app_head_obj;

// 复位不被初始化的变量，用于BOOT判断是否由APP执行的软件复位
__attribute__((zero_init)) uint32_t app_start_en __attribute__((at(0x20003800)));

// 复位定时器
EASY_TIMER_DEF(timer_reset);

// 定时器计数
uint16_t reset_cnt = 0;
uint16_t reset_enable = 0;

static void reset_delay(void)
{
    if (reset_enable == 0) {
        return;
    }

    if (++reset_cnt > 60) { // 1分钟后复位
        RCC_Reset_Flag_Clear();
        __NVIC_SystemReset();
    }
}

/**
 * @description: BOOT初始化，判断APP是否存在，以及是否跳转至APP
 */
void boot_init(void)
{
    boot.current_addr = 0;
    boot.seq = 0;
    boot.status = BOOT_STATUS_INIT;

    bsp_flash_obj_init(&app1_obj, APP1_BASE_ADDR, APP1_SIZE);
    bsp_flash_obj_init(&app2_obj, APP2_BASE_ADDR, APP2_SIZE);
    bsp_flash_obj_init(&app_head_obj, APP_HEAD_ADDR, APP_HEAD_SIZE);

    // 看门狗复位，则使能复位定时器，在1分钟后复位
    if (RCC_Flag_Status_Get(RCC_FLAG_IWDGRST) == 1) {
        RCC_Reset_Flag_Clear();
        log_debug("Reset by IWDG, not jump to APP\n");
        reset_cnt = 0;
        reset_enable = 1;
        easy_timer_create(timer_reset);
        easy_timer_start(timer_reset, 1000, EASY_TIMER_MODE_REPEATED, reset_delay);
        return;
    }

    // MMU复位
    if (RCC_Flag_Status_Get(RCC_FLAG_MMURST) == 1) {
        RCC_Reset_Flag_Clear();
        log_debug("Reset by MMU, not jump to APP\n");
    }

    // 如果是软件复位，则判断是APP还是BOOT执行的复位
    // 如果是由APP执行的复位，则不允许跳转APP
    if (RCC_Flag_Status_Get(RCC_FLAG_SFTRST) == 1) {
        RCC_Reset_Flag_Clear();
        if (app_start_en == 0x5A5A5A5A) { // app_start_en由APP执行复位时写为0x5A5A5A5A
            log_debug("Reset by APP, not jump to APP\n");
            app_start_en = 0xA5A5A5A5;
            return;
        }
    }

    log_debug("trying jump to app\n");
    boot_jumo_app(); // 跳转至APP
    log_debug("jump fail\n");
}

static ErrorStatus save_app_info(void)
{
    app_head.crc = boot.crc;
    app_head.size = boot.current_addr;

    app_head.crc_bkp = boot.crc;
    app_head.size_bkp = boot.current_addr;

    // 擦除APP2
    if (bsp_flash_page_erase(&app2_obj, 0, APP2_PAGE_NUM) == ERROR) {
        log_debug("erase app2 fail...\n");
        return ERROR;
    }

    // 复制APP1到APP2
    if (bsp_flash_write(&app2_obj, 0, (uint8_t *)app1_obj.base_addr, app_head.size) == ERROR) {
        log_debug("copy app2 fail...\n");
        return ERROR;
    }

    // 保存APP长度 CRC校验码等信息，供BOOT启动时调取
    if (bsp_flash_page_erase(&app_head_obj, 0, 1) == ERROR) {
        log_debug("erase app head fail...\n");
        return ERROR;
    }

    if (bsp_flash_write(&app_head_obj, 0, (uint8_t *)&app_head, sizeof(app_head_t)) == ERROR) {
        log_debug("save app head fail...\n");
        return ERROR;
    }

    boot.current_addr = 0;
    boot.seq = 0;
    boot.status = BOOT_STATUS_INIT;

    return SUCCESS;
}

ErrorStatus boot_app_erase(void)
{
    ErrorStatus rtn;
    log_debug("boot_app_erase..\n");
    boot.current_addr = 0;
    boot.seq = 0;
    boot.status = BOOT_STATUS_ERASE;

    easy_timer_stop(timer_reset);
    reset_enable = 0;


    rtn = bsp_flash_page_erase(&app1_obj, 0, APP1_PAGE_NUM);
    
    if (rtn == SUCCESS) {
        boot.status = BOOT_STATUS_PROGRAM;
    }
    return rtn;
}

ErrorStatus boot_app_program(uint8_t * buf, uint16_t len)
{
    ErrorStatus rtn = ERROR;
    log_debug("boot_app_program..\n");
    if (boot.status == BOOT_STATUS_PROGRAM) {
        uint16_t seq = (buf[0] >> 8) + buf[1];

        uint16_t crc1 = (buf[2] << 8) + buf[3];
        uint16_t crc2 = crc16(buf + 6, 32);

        if (crc1 != crc2) {
            log_debug("bin crc fail..\n");
            return ERROR;
        }
        rtn = bsp_flash_write(&app1_obj, boot.current_addr, buf + 6, 32);
        log_debug("addr = %04x, seq = %d\n", boot.current_addr, boot.seq);
        if (rtn == SUCCESS) {
            boot.current_addr += 32;
            boot.seq = seq;
        } else {
            log_debug("program fail..\n");
        }
    } 

    return rtn;
}

ErrorStatus boot_app_check_crc(uint16_t crc)
{
    ErrorStatus rtn = ERROR;
    log_debug("boot_app_check_crc..\n");

    if (boot.status != BOOT_STATUS_PROGRAM || boot.current_addr == 0) {
        return ERROR;
    }

    uint16_t crc1 = crc16((uint8_t *)app1_obj.base_addr, boot.current_addr);

    log_debug("crc = %04x, crc1 = %04x\n", crc, crc1);
    if (crc1 == crc) {
        boot.crc = crc;
        rtn = save_app_info(); // 记录APP校验信息和备份新APP
    }

    return rtn;
}

ErrorStatus boot_jumo_app(void)
{
    uint16_t crc_cal;
    // 检查程序是否合法
    bsp_flash_read(&app_head_obj, 0, (uint8_t *)&app_head, sizeof(app_head_t)); // 读出文件头

    // 检查APP1
    if (app_head.size <= APP1_SIZE && app_head.size >= FLASH_PAGE_SIZE) {
        crc_cal = crc16((uint8_t *)app1_obj.base_addr, app_head.size); // 计算APP1的CRC
        if (crc_cal == app_head.crc) { // CRC校验通过，跳转至APP
            bsp_jump_to(app1_obj.base_addr);
        }
    }

    log_debug("app error, stop jump app, now check the app backup...\n");

    // 检查APP2
    if (app_head.size_bkp <= APP2_SIZE && app_head.size_bkp >= FLASH_PAGE_SIZE) {
        crc_cal = crc16((uint8_t *)app2_obj.base_addr, app_head.size_bkp); // 计算APP1的CRC
        if (crc_cal == app_head.crc_bkp) { // CRC校验通过，跳转至APP
            log_debug("app backup OK, copy app2 to app1\n");
            // 擦除APP1
            if (bsp_flash_page_erase(&app1_obj, 0, APP1_PAGE_NUM) == ERROR) {
                log_debug("erase app1 fail...\n");
                return ERROR;
            }

            // 复制APP2到APP1
            if (bsp_flash_write(&app1_obj, 0, (uint8_t *)app2_obj.base_addr, app_head.size_bkp) == ERROR) {
                log_debug("copy app2 fail...\n");
                return ERROR;
            }
            app_head.crc = app_head.crc_bkp;
            app_head.size = app_head.size_bkp;
            // 保存APP长度 CRC校验码等信息，供BOOT启动时调取
            if (bsp_flash_page_erase(&app_head_obj, 0, 1) == ERROR) {
                log_debug("erase app head fail...\n");
                return ERROR;
            }

            if (bsp_flash_write(&app_head_obj, 0, (uint8_t *)&app_head, sizeof(app_head_t)) == ERROR) {
                log_debug("save app head fail...\n");
                return ERROR;
            }

            log_debug("copy complete, jump to app1\n");
            bsp_jump_to(app1_obj.base_addr);
        }
    }

    log_debug("app backup error, stop jump\n");

    return ERROR;
}
